package com.example.socks5_speed;


import android.net.VpnService;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ClientOrServer {

    static ExecutorService executorService = Executors.newCachedThreadPool();

    static String type = ""; // 可以设置为 "server" 或 "client"，根据需要切换

    public static void main(String[] args) throws IOException {

        // 启动命令示例：java -Dtype=server org.example.ClientOrServer
        // 读取系统属性
        type = System.getProperty("type","client");

        try (var serverSocket = new ServerSocket(8888)) {
            System.out.println("Socks5 代理服务器已启动，监听端口: 8888");
            while (!Thread.interrupted()) {
                var socket = serverSocket.accept();
                executorService.submit(() -> {
                    clientHandler(socket);
                });
            }
        }
    }

    private static VpnService _vpnService;
    public static void start(VpnService vpnService){
        type ="client";
        _vpnService = vpnService;
        try (var serverSocket = new ServerSocket(8888)) {
            System.out.println("Socks5 代理服务器已启动，监听端口: 8888");
            while (!Thread.interrupted()) {
                var socket = serverSocket.accept();
                _vpnService.protect(socket);
                executorService.submit(() -> {
                    clientHandler(socket);
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private static void clientHandler(Socket socket) {

        try (socket) {

            negotiatedConnection(socket);

            connectServer(socket);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void negotiatedConnection(Socket socket) throws IOException {
        //按需读取数据
        InputStream inputStream = socket.getInputStream();
        //协议判断
        if (inputStream.read() != 5) {
            throw new RuntimeException("只支持socks5协议!!");
        }
        //认证方式
        int authMethodCount = inputStream.read();
        boolean noAuth = false;
        for (int i = 0; i < authMethodCount; i++) {
            int data = inputStream.read();
            if (data == 0) {
                noAuth = true;
            }
        }
        if (!noAuth) {
            throw new RuntimeException("只支持无认证方式!!");
        }
        //响应无需验证
        socket.getOutputStream().write(new byte[]{5, 0});
    }

    private static void connectServer(Socket socket) throws IOException {

        InputStream inputStream = socket.getInputStream();

        int ver = inputStream.read();
        int cmd = inputStream.read();
        int rsv = inputStream.read();
        int atyp = inputStream.read();
        String host = "";

        //用于回复:成功
        var replyStream  = new ByteArrayOutputStream();
        replyStream.write(ver);
        replyStream.write(0);
        replyStream.write(rsv);
        replyStream.write(atyp);

        if (atyp == 1) {//IPv4 地址
            byte[] data = null;
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
                data = inputStream.readNBytes(4);
            }
            host = InetAddress.getByAddress(data).getHostAddress();
            replyStream.write(data);
        } else if (atyp == 3) {//表示域名
            int len = inputStream.read();
            byte[] data = null;
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
                data = inputStream.readNBytes(len);
            }
            host = new String(data);
            replyStream.write(len);
            replyStream.write(data);
        } else if (atyp == 4) {//IPv6 地址
            byte[] data = null;
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
                data = inputStream.readNBytes(16);
            }
            host = InetAddress.getByAddress(data).getHostAddress();
            replyStream.write(data);
        } else {
            throw new RuntimeException("不支持的地址类型: " + atyp);
        }
        byte[] data = null;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
            data = inputStream.readNBytes(2);
        }
        int port = ((data[0] & 0xFF) << 8) | (data[1] & 0xFF);
        replyStream.write(data);
        System.out.println("请求连接: " + host + ":" + port);

        MyVpnService myVpnService = (MyVpnService) _vpnService;
        host = myVpnService.ipAndHostMap.get(host);

        Proxy proxy = Proxy.NO_PROXY;
        host = xorEncrypt(host, 'c'); // 使用异或加密
        System.out.println("位运算后的主机: " + host);



        if(type.equals("client")) {
            proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("43.255.29.253", 8888));
        }

        Socket targetSocket = new Socket(proxy);
        targetSocket.setSoTimeout(10000);

        _vpnService.protect(targetSocket);
        targetSocket.connect(new InetSocketAddress(host,port));
        //响应连接成功
        socket.getOutputStream().write(replyStream.toByteArray());

        executorService.submit(() -> {
            //转发数据
            try (targetSocket;socket) {
                transferTo(targetSocket.getInputStream(), socket.getOutputStream());
            } catch (Exception e) {
//                e.printStackTrace();
            }

        });
        //转发数据
        try (targetSocket){
            transferTo(socket.getInputStream(), targetSocket.getOutputStream());
        }catch (Exception e) {
//            e.printStackTrace();
        }

    }

    private static void transferTo(InputStream in,OutputStream out) throws IOException {
        byte[] buffer = new byte[1024];
        int read;
        while ((read = in.read(buffer, 0, buffer.length)) >= 0) {

            // 对每个字节进行异或加密
            for (int i = 0; i < read; i++) {
                buffer[i] = (byte) (buffer[i] ^ 'c'); // 异或运算加密
            }
            out.write(buffer, 0, read);
        }
    }

    public static String xorEncrypt(String input, char key) {
        char[] chars = input.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            chars[i] = (char) (chars[i] ^ key); // 使用异或运算
        }
        return new String(chars);
    }

}
